#include "rss.h"
#include "url.h"
#include "date.h"
#include "utilities.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlreader.h>

///////////////////////////////////////////////////////////////////////////////

// #define FILE_TRACE

#include "file_trace.h"

///////////////////////////////////////////////////////////////////////////////

//------------------------------------------------------------------------------
// defines
//------------------------------------------------------------------------------

#define RSS_PUB_DATE                      "_pubDate_lastBuildDate"
#define HTML_DIGEST_TITLE                 "AgileRSS Digest"
#define HTML_DIGEST_BACKGROUND_COLOR      "#FFFF99"
#define HTML_DIGEST_FONT                  "verdana"

//------------------------------------------------------------------------------
// enums
//------------------------------------------------------------------------------

enum
{
    RSS_DIGEST_CHUNK = 1 * 1024,
    RSS_FORMAT_CHUNK = 3 * 1024,
    BUFFER_LOCAL_TIME_LENGTH = 1 * 512,
	MAX_KEYWORD_LENGTH = 1 * 1024
};

//------------------------------------------------------------------------------
// static variables
//------------------------------------------------------------------------------

static feed_t*  s_feeds = NULL;
static const char**  s_feed_sources = NULL;
static int  s_feed_count = 0;
static customer_t*  s_customers = NULL; 
static int  s_customer_count = 0;

//------------------------------------------------------------------------------
// static functions prototypes
//------------------------------------------------------------------------------

static void
s_InsertItem(feed_t* a_feed, int a_index, xmlNodePtr a_node, const xmlDocPtr a_doc);

static bool
s_ExtractData(int a_index, xmlNodePtr a_node, const xmlDocPtr a_doc);

static bool
s_ProcessMemory(int a_index, memory_t* a_memory);

static bool
s_DownloadFeed(int a_index, const char* a_url);

static bool
s_CopyToDigest(int a_customer, char* a_buffer);

static void
s_FreeItems(int a_index);

static int
s_CompareFeedDate(const void* p1, const void* p2);

static int
s_CompareItemDate(const void* p1, const void* p2);

static void
s_Normalize(char* a_text);

static void
s_TrimBlanks(char* a_text);

static void
s_Clean(char* a_text);

bool
s_ConvertPubdatetoLocalTime(char* pubdate, char* time_zone, char buffer_localtime[]);

bool
s_KeywordSearch(int feed, int item, int customer, char* inc_key);

void
s_AddItems(int a_customer, int a_keyword, bool a_has_keyword);

static void
s_SimpleKeyword(int a_customer, int a_keyword);

static void
s_ComplexKeyword(int a_customer, int a_keyword);

//------------------------------------------------------------------------------
// public functions
//------------------------------------------------------------------------------

bool
RSS_Init(const char** a_feed_sources, 
         int a_feed_count, 
         customer_t* a_customers, 
         int a_customer_count)
{
    bool result = false;

    // check the source data: if no feed source is provided,  
    // assume RSS_LoadFile() will be called
    if (a_feed_sources == NULL && a_feed_count != 1)
    {
        F_ERROR(("bad feed sources!\n"));
        goto exit;
    }

    // check the customer data
    if (a_customers == NULL)
    {
        F_ERROR(("bad customers!\n"));
        goto exit;
    }

    if  (a_customer_count <= 0)
    {
        F_ERROR(("bad customer count!\n"));
        goto exit;
    }

    // save the params
    s_feed_sources = a_feed_sources;
    s_feed_count = a_feed_count;
    s_customers = a_customers; 
    s_customer_count = a_customer_count;

    result = true;

exit:

    return result;
}

bool
RSS_DownloadFeeds()
{
    bool result = false;
    int  populated = 0;
    int  i;

    F_PRINT(("start\n"));

    // try to allocate memory for the feeds
    s_feeds = (feed_t*) malloc(s_feed_count * sizeof(feed_t));
    if (s_feeds == NULL)
    {
        F_ERROR(("malloc() failed!\n"));
        goto exit;
    }

    // reset
    memset(s_feeds, 0, s_feed_count * sizeof(feed_t));

    // one-by-one access rss feeds and extract data
    for (i = 0; i < s_feed_count; i++)
    {
        F_PRINT(("url:|%s|\n", s_feed_sources[i]));

        if (s_DownloadFeed(i, s_feed_sources[i]))
        {
            populated++;
        }
    }

    F_PRINT(("source count, populated sources and verdict:|%d|%d|%s|\n",
             s_feed_count, populated, (populated == s_feed_count) ? "all ok" : "some failed"));

    if (populated == s_feed_count)
    {
        result = true;
    }

exit:

    F_PRINT(("end\n"));

    return result;
}

void
RSS_SortFeedsbyDate()
{
	int i;

	qsort(s_feeds, s_feed_count, sizeof(struct feed), s_CompareFeedDate);

	for (i = 0; i < s_feed_count; i++)
	{
		F_PRINT(("feed[%d] pubdate:|%s|  title:|%s|\n", i, s_feeds[i].pubdate, s_feeds[i].title));

		qsort(s_feeds[i].items, s_feeds[i].itemcount, sizeof(struct item), s_CompareItemDate);
	}
}

const feed_t*
RSS_GetFeeds()
{
    return s_feeds;
}

bool
RSS_CreateDigests()
{			     
	char  temp[MAX_KEYWORD_LENGTH] = {0};
    bool  result = false;
	int  customer;
	int  keyword_count;
	int  keyword;

    // one-by-one process the customers
    for (customer = 0; customer < s_customer_count; customer++)
    {
		// first count the keywords for the customer
        for (keyword_count = 0; s_customers[customer].keywords[keyword_count] != NULL; keyword_count++)
			;
			
		F_PRINT(("customer[%d] keyword count:|%d|\n", customer, keyword_count));
		
		// try to allocate memory for the array of item pointer arrays
		s_customers[customer].raw_digest = (item_t***) malloc(keyword_count * sizeof(item_t**));
		if (s_customers[customer].raw_digest == NULL)
		{
			F_ERROR(("malloc() failed!\n"));

			goto exit;
		}
		
		// reset
		memset(s_customers[customer].raw_digest, 0, keyword_count * sizeof(item_t**));		
		
        // one-by-one check the keywords for the customer
        for (keyword = 0; keyword < keyword_count; keyword++)
        {
            F_PRINT(("customer[%d]  keyword[%d]:|%s|\n",
                     customer,
                     keyword,
                     s_customers[customer].keywords[keyword]));

			// is the keyword "simple" (single word) or "complex" (multiple words)?
			if (strchr(s_customers[customer].keywords[keyword], ' ') == NULL)
			{
                s_SimpleKeyword(customer, keyword);
            }
			else 
			{
				strcpy(temp, s_customers[customer].keywords[keyword]);
                s_ComplexKeyword(customer, keyword);
				strcpy(s_customers[customer].keywords[keyword], temp);
			}
		} // for keyword
	} // for customer

	result = true;
		
exit:

	return result;
}

bool
RSS_TextFormatDigest(int a_customer)
{
	char  buffer[RSS_FORMAT_CHUNK] = {0};
    char  localtime[BUFFER_LOCAL_TIME_LENGTH] = {0};
    bool  result = false;
	int  keyword;
	int  item;

	// one-by-one check the keywords for the customer		
    for (keyword = 0; s_customers[a_customer].keywords[keyword] != NULL; keyword++)
	{	
		// reset buffer
		memset(buffer, 0, RSS_FORMAT_CHUNK);
		
		if (s_customers[a_customer].raw_digest[keyword] == NULL)
		{
			_snprintf(buffer,
			          RSS_FORMAT_CHUNK - 1,
			          "********************************************************************************\n"
			          " no item found for keyword |%s|\n"
			          "********************************************************************************\n\n",
			          s_customers[a_customer].keywords[keyword]);
			        
			 if (s_CopyToDigest(a_customer, buffer) == false)
                 goto exit;  
		}
		else
		{
			// first count the digest items for the customer
			for (item = 0; s_customers[a_customer].raw_digest[keyword][item] != NULL; item++)
				;

			_snprintf(buffer,
			          RSS_FORMAT_CHUNK - 1,
			          "********************************************************************************\n"
			          " %d item%s found for keyword |%s|\n"
			          "********************************************************************************\n\n",
			          item,
			          (item > 1 ? "s" : ""),
			          s_customers[a_customer].keywords[keyword]);
			                
			if (s_CopyToDigest(a_customer, buffer) == false)
                goto exit;
							
			// one-by-one check the digest items for the customer
			for (item = 0; s_customers[a_customer].raw_digest[keyword][item] != NULL; item++)
			{
				// reset buffer
				memset(buffer, 0, RSS_FORMAT_CHUNK);

                // clean the texts
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->title);
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->description);
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->url);
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->pubdate);

				// get the localtime for the user
                if (!s_ConvertPubdatetoLocalTime(s_customers[a_customer].raw_digest[keyword][item]->pubdate, s_customers[a_customer].time_zone, localtime))
				{
					goto exit;
				}

				_snprintf(buffer,
			              RSS_FORMAT_CHUNK - 1,
			              "item %d\n"
                          "    title: %s\n"
                          "    desc: %s\n"
					      "    local time: %s\n"
						  "    link: %s\n\n", 
					      item + 1,
					      s_customers[a_customer].raw_digest[keyword][item]->title,
					      s_customers[a_customer].raw_digest[keyword][item]->description,
						  localtime,
					      s_customers[a_customer].raw_digest[keyword][item]->url);
       
				if (s_CopyToDigest(a_customer, buffer) == false)
					goto exit;
			} // for item
		}
	} // for keyword   
	
	result = true;
		
exit:

	return result;
}

bool
RSS_HTMLFormatDigest(int a_customer)
{
	char  buffer[RSS_FORMAT_CHUNK] = {0};
    char  localtime[BUFFER_LOCAL_TIME_LENGTH] = {0};
    bool  result = false;
	int  keyword;
	int  item;

	// init the html page
	_snprintf(buffer,
			  RSS_FORMAT_CHUNK - 1,
			  "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
			  "<html>\n"
			  "<head>\n"
			  "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n"
              "    <title>%s</title>\n"
              "</head>\n"
              "<body bgcolor=%s>\n"
			  "<font face=\"%s\">\n"
			  "<h1>%s</h1>\n\n",
              HTML_DIGEST_TITLE,
              HTML_DIGEST_BACKGROUND_COLOR,
			  HTML_DIGEST_FONT,
			  HTML_DIGEST_TITLE);

	if (s_CopyToDigest(a_customer, buffer) == false)
        goto exit;  

	// one-by-one check the keywords for the customer		
    for (keyword = 0; s_customers[a_customer].keywords[keyword] != NULL; keyword++)
	{	
		// reset buffer
		memset(buffer, 0, RSS_FORMAT_CHUNK);
		
		if (s_customers[a_customer].raw_digest[keyword] == NULL)
		{
			_snprintf(buffer,
			          RSS_FORMAT_CHUNK - 1,
					  "<hr>\n"
					  "No item found for keyword <font color=red><b>%s</b></font>.<br>\n\n",
			          s_customers[a_customer].keywords[keyword]);
			        
			 if (s_CopyToDigest(a_customer, buffer) == false)
                 goto exit;  
		}
		else
		{
			// first count the digest items for the customer
			for (item = 0; s_customers[a_customer].raw_digest[keyword][item] != NULL; item++)
				;

			_snprintf(buffer,
			          RSS_FORMAT_CHUNK - 1,
					  "<hr>\n"
					  "<b>%d</b> item%s found for keyword <font color=red><b>%s</b></font>:<br><br>\n",
			          item,
			          (item > 1 ? "s" : ""),
			          s_customers[a_customer].keywords[keyword]);
			                
			if (s_CopyToDigest(a_customer, buffer) == false)
                goto exit;
							
			// one-by-one check the digest items for the customer
			for (item = 0; s_customers[a_customer].raw_digest[keyword][item] != NULL; item++)
			{
				// reset buffer
				memset(buffer, 0, RSS_FORMAT_CHUNK);

                // clean the texts
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->title);
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->description);
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->url);
                s_Clean(s_customers[a_customer].raw_digest[keyword][item]->pubdate);

				// get the localtime for the user
                if (!s_ConvertPubdatetoLocalTime(s_customers[a_customer].raw_digest[keyword][item]->pubdate, s_customers[a_customer].time_zone, localtime))
				{
					goto exit;
				}

				// dump the item
				_snprintf(buffer,
						RSS_FORMAT_CHUNK - 1,
						"<b>item %d</b><br>\n"
						"<b>title:</b> %s<br>\n"
						"<b>desc:</b> %s<br>\n"
						"<b>local time:</b> %s<br>\n",
						item + 1,
						s_customers[a_customer].raw_digest[keyword][item]->title,
						s_customers[a_customer].raw_digest[keyword][item]->description,
						localtime);
       
				if (s_CopyToDigest(a_customer, buffer) == false)
					goto exit;

				// is there an image?
				if (s_customers[a_customer].raw_digest[keyword][item]->img_url)
				{
					_snprintf(buffer,
								RSS_FORMAT_CHUNK - 1,
								"<br><img src=\"%s\" /><br><br>\n", 
								s_customers[a_customer].raw_digest[keyword][item]->img_url);

					if (s_CopyToDigest(a_customer,buffer) == false)
						goto exit;
				}

				 // write the link
				_snprintf(buffer,
			              RSS_FORMAT_CHUNK - 1,
						  "<a href=\"%s\">%s</a><br><br>\n\n", 
					      s_customers[a_customer].raw_digest[keyword][item]->url,
						  s_customers[a_customer].raw_digest[keyword][item]->url);
       
				if (s_CopyToDigest(a_customer, buffer) == false)
					 goto exit;
			} // for item
		}
	} // for keyword   
	
	// terminate the "body"
	if (s_CopyToDigest(a_customer, "</font>\n</body>\n</html>\n\n") == false)
		goto exit;

	result = true;
		
exit:

	return result;
}

bool
RSS_LoadFeed(const char* a_file)
{
    bool  result = false;
    FILE*  handle = NULL;
    memory_t  memory = {0};
    size_t  read = 0;

    F_PRINT(("start\n"));

    // check the file specified
    if (a_file == NULL)
    {
        F_ERROR(("bad file!\n"));

        goto exit;
    }

    handle = fopen(a_file, "r");
    if (handle == NULL)
    {
        F_ERROR(("failed to open the file |%s|!\n", a_file));

        goto exit;
    }

    // get the size of the file to read all names at once
    if (fseek(handle, 0, SEEK_END) != 0)
    {
        F_ERROR(("fseek() failed!\n"));

        goto exit;
    }

    memory.size = ftell(handle);
    if (memory.size == -1 || memory.size == 0)
    {
        F_ERROR(("ftell() failed!\n"));

        goto exit;
    }

    // reset the file position indicator
    rewind(handle);

    // allocate memory for the file contents
    memory.retrieved = (char*) malloc(sizeof(char)* memory.size);
    if (memory.retrieved == NULL)
    {
        F_ERROR(("malloc() failed!\n"));

        goto exit;
    }

    // reset
    memset(memory.retrieved, 0, sizeof(char)* memory.size);

    // try to allocate memory for the feed
    s_feeds = (feed_t*) malloc(sizeof(feed_t));
    if (s_feeds == NULL)
    {
        F_ERROR(("malloc() failed!\n"));

        goto exit;
    }

    // reset
    memset(s_feeds, 0, sizeof(feed_t));

    // load the file contents
    read = fread(memory.retrieved, sizeof(char), memory.size, handle);
    if (read != memory.size)
    {
        F_ERROR(("fread() failed!\n"));

        goto exit;
    }

    // now process memory
    result = s_ProcessMemory(0, &memory);

exit:

    F_PRINT(("end\n"));

    if (handle)
    {
        fclose(handle);
        handle = NULL;
    }

    if (memory.retrieved)
    {
        free(memory.retrieved);
        memory.retrieved = NULL;
    }

    return result;
}

void
RSS_Free()
{
    int  feed;

    // no memory leak!

    // first, one-by-free items memory for each feed
    for (feed = 0; feed < s_feed_count; feed++)
    {
        // free the feed info
        xmlFree(s_feeds[feed].title);
        xmlFree(s_feeds[feed].pubdate);
        xmlFree(s_feeds[feed].language);

        s_feeds[feed].title = NULL;
        s_feeds[feed].pubdate  = NULL;
        s_feeds[feed].language = NULL;

        // free the feed items
        s_FreeItems(feed);
        free(s_feeds[feed].items);
        s_feeds[feed].items = NULL;
    }

    // second, free the feeds memory
    free(s_feeds);
    s_feeds = NULL;

	// reset
	s_feed_count = 0;
	s_customer_count = 0;
}

void
RSS_LogStatistics()
{
 	int customer;
	int keyword;
    int item_count;
    char * timestamp = DATE_GetSystemDateTime();
    FILE * log_file;
    char * statistics_log_path = RSS_GetStatisticsFilePath();

    log_file = fopen(statistics_log_path, "a");

    if (log_file == NULL)
    {
        F_ERROR(("fopen(%s) failed!\n", statistics_log_path));
        free(statistics_log_path);
        free(timestamp);
        return;
    }
    
    fprintf(log_file, "[%s]\n", timestamp);
    
    for (customer = 0; customer < s_customer_count; customer++)
    {
        for (keyword = 0; s_customers[customer].keywords[keyword] != NULL; keyword++)
        {
            // count number of hits for current customer and keyword
            for (item_count = 0; s_customers[customer].raw_digest[keyword] != NULL && 
                s_customers[customer].raw_digest[keyword][item_count] != NULL; item_count++)
			{
			}

            fprintf(log_file, "The keyword '%s' got %d hits for %s %s.\n",
                   s_customers[customer].keywords[keyword],
                   item_count,
                   s_customers[customer].name,
                   s_customers[customer].surname);
		}
	}

    fprintf(log_file, "\n", timestamp);

    fclose(log_file);
    free(statistics_log_path);
    free(timestamp);
}

char *
RSS_GetStatisticsFilePath()
{
    char * statistics_log_path = (char *)malloc(MAX_PATH_LENGTH);
    if (statistics_log_path)
    {
        _snprintf(statistics_log_path, MAX_PATH_LENGTH - 1, "%s\\%s", INI_GetDataPath(), STATISTICS_FILE_NAME);
    }

    return statistics_log_path;
}

//------------------------------------------------------------------------------
// sattic function
//------------------------------------------------------------------------------

static void
s_InsertItem(feed_t* a_feed, int a_index, xmlNodePtr a_node, const xmlDocPtr a_doc)
{
    char*  temp = NULL;
    xmlNodePtr  node;
    xmlNodePtr  desc;
    xmlNodePtr  encoded;
    int  i = 0;

    // make sure that node has children
    node = a_node->children;
    if (node == NULL)
    {
        F_ERROR(("no children!\n"));
        goto exit;
    }

    // extract data and populate rss item
    for ( ; node; node = node->next)
    {
        if (strcmp((char*) node->name, "title") == 0)
        {
            temp = (char*) xmlNodeListGetString(a_doc, node->children, 1);
            if (temp != NULL)
            {
                strncpy(a_feed->items[a_index].title, temp, RSS_SIZE_HALF - 1);
                xmlFree(temp);
            }
        }
        else if (strcmp((char*) node->name, "link") == 0)
        {
            // more than one link is possible! e.g. <atom::link href="..." .../> 
            // which sets the url as a property not node string! ignore this kind of links!
            if (a_feed->items[a_index].url == NULL)
            {
                temp = (char*) xmlNodeListGetString(a_doc, node->children, 1);
                if (temp)
                {
                    a_feed->items[a_index].url = temp;
                }
            }
        }
        else if (strcmp((char*) node->name, "description") == 0)
        {
            desc = node;

            // if there is an "encoded" node, use it instead!
            for (encoded = a_node->children; encoded; encoded = encoded->next)
            {
                if (strcmp((char*) encoded->name, "encoded") == 0)
                {
                    desc = encoded;
                    break;
                }
            }

            temp = (char*) xmlNodeListGetString(a_doc, desc->children, 1);
            if (temp != NULL)
            {
                strncpy(a_feed->items[a_index].description, temp, RSS_SIZE - 1);
                xmlFree(temp);
            }
        }
        else if (strcmp((char*) node->name, "pubDate") == 0)
        {
            a_feed->items[a_index].pubdate = (char*) xmlNodeListGetString(a_doc, node->children, 1);
        }
        else if (strcmp((char*) node->name, "thumbnail") == 0)
        {
            a_feed->items[a_index].img_url = (char*) xmlGetProp(node, "url");
        }
    }

    F_PRINT(("item:\n"
             "    title      :|%s|\n"
             "    link       :|%s|\n"
             "    description:|%s|\n"
             "    pubdate    :|%s|\n"
             "    image url  :|%s|\n",
             a_feed->items[a_index].title,
             a_feed->items[a_index].url,
             a_feed->items[a_index].description,
             a_feed->items[a_index].pubdate,
             a_feed->items[a_index].img_url));

exit:

    return;
}

static bool
s_ExtractData(int a_index, xmlNodePtr a_node, const xmlDocPtr a_doc)
{
    xmlNodePtr  node;
    bool  result = false;
    int   i;

    F_PRINT(("start\n"));

    // check arguments
    if (a_node == NULL || a_doc == NULL)
    {
        F_ERROR(("null argument!\n"));
        goto exit;
    }

    // count items
    for (node = a_node; node; node = node->next)
    {
        if (strcmp((char*) node->name, "item") == 0)
        {
			s_feeds[a_index].itemcount++;
        }
    }

    if (s_feeds[a_index].itemcount == 0)
    {
        F_ERROR(("no item found!\n"));
        goto exit;
    }

    F_PRINT(("item count:|%d|\n", s_feeds[a_index].itemcount));

    // try to allocate memory for items
    s_feeds[a_index].items = (item_t*) malloc(s_feeds[a_index].itemcount * sizeof(item_t));
    if (s_feeds[a_index].items == NULL)
    {
        F_ERROR(("malloc() failed!\n"));
        goto exit;
    }

    // reset
    memset(s_feeds[a_index].items, 0, s_feeds[a_index].itemcount * sizeof(item_t));

    // extract data
    for (node = a_node, i = 0; node && i < s_feeds[a_index].itemcount; node = node->next)
    {
        if (strcmp((char*) node->name, "title") == 0)
        {
            s_feeds[a_index].title = (char*) xmlNodeListGetString(a_doc, node->children, 1);

            F_PRINT(("feed title   :|%s|\n", s_feeds[a_index].title));
        }
        else if (strstr(RSS_PUB_DATE, (char*) node->name) != NULL)
        {
            s_feeds[a_index].pubdate = (char*) xmlNodeListGetString(a_doc, node->children, 1);

            F_PRINT(("feed pubdate :|%s|\n", s_feeds[a_index].pubdate));
        }
        else if (strcmp((char*) node->name, "language") == 0)
        {
            s_feeds[a_index].language = (char*) xmlNodeListGetString(a_doc, node->children, 1);

            F_PRINT(("feed language:|%s|\n", s_feeds[a_index].language));
        }
        else if (strcmp((char*) node->name, "item") == 0)
        {
            s_InsertItem(&(s_feeds[a_index]), i++, node, a_doc);
        }
    }

    result = true;

    F_PRINT(("ok...\n"));

exit:

    F_PRINT(("end\n"));

    return result;
}

static bool
s_ProcessMemory(int a_index, memory_t* a_memory)
{
    xmlDocPtr  doc = NULL;
    xmlNodePtr  root = NULL;
    xmlNodePtr  node = NULL;
    char*  version = NULL;
    bool  result = false;

    F_PRINT(("start\n"));

    // reset
    s_feeds[a_index].itemcount = 0;

    // read memory
    doc = xmlReadMemory(a_memory->retrieved, a_memory->size, "noname.xml", NULL, 0);
    if (doc == NULL)
    {
        F_ERROR(("xmlReadMemory() failed!\n"));
        goto exit;
    }

    // get root
    root = xmlDocGetRootElement(doc);
    if (root == NULL)
    {
        F_ERROR(("no root element!\n"));
        goto exit;
    }

    F_PRINT(("root name:|%s|\n", root->name));
    if (strcmp((char*) root->name, "rss") != 0)
    {
        F_ERROR(("bad root response!\n"));
        goto exit;
    }

    version = (char*) xmlGetProp(root, (xmlChar*) "version");
    if (version == NULL)
    {
        F_ERROR(("no version!\n"));
        goto exit;
    }

    F_PRINT(("version:|%s|\n", version));
    if (strcmp(version, "2.0") != 0)
    {
        F_ERROR(("version not ok!\n"));
        goto exit;
    }

    // go deeper and deeper
    node = root->children;
    if (node == NULL)
    {
        F_ERROR(("no children!\n"));
        goto exit;
    }

    F_PRINT(("node name:|%s|\n", (char*) node->name));

    // @todo: why this check is necessary?
    if (strcmp((char*) node->name, "text") == 0)
    {
        node = node->next;
	}
	
    node = node->children;
    if (node == NULL)
    {
        F_ERROR(("no children!\n"));
        goto exit;
    }

    // @todo: why this check is necessary?
    if (strcmp((char*) node->name, "text") == 0)
    {
        node = node->next;
	}
	
    F_PRINT(("node name:|%s|\n", (char*) node->name));

    // now extract rss data
    result = s_ExtractData(a_index, node, doc);

exit:

    if (doc)
    {
        xmlFreeDoc(doc);
	}
	
    if (version)
    {
        xmlFree(version);
	}
	
    F_PRINT(("end\n"));

    return result;
}

static bool
s_DownloadFeed(int a_index, const char* a_url)
{
    memory_t  memory = {0};
    bool  result = false;

    // check arguments
    if (a_index < 0)
    {
        F_ERROR(("bad index!\n"));
        goto exit;
    }

    if (a_url == NULL)
    {
        F_ERROR(("bad url!\n"));
        goto exit;
    }

    F_PRINT(("url:|%s|\n", a_url));

    // get to memory
    memory.url = a_url;
    if (!URL_DownloadToMemory(&memory, 0, 0))
    {
        goto exit;
	}
	
    // now process memory
    result = s_ProcessMemory(a_index, &memory);

exit:

    // no memory leak!
    if (memory.retrieved)
    {
        free(memory.retrieved);
        memory.retrieved = NULL;
        memory.size = 0;
    }

    F_PRINT(("end\n"));

    return result;
}

static bool
s_CopyToDigest(int a_customer, char* a_buffer)
{
    int  digest_len = s_customers[a_customer].formatted_digest == 0 ? 0 : strlen(s_customers[a_customer].formatted_digest);   
	int  buffer_len = strlen(a_buffer);
	bool  result = false;    
	
	// try to realloc more memory  
	s_customers[a_customer].formatted_digest = (char*) realloc(s_customers[a_customer].formatted_digest,
                                                               digest_len + buffer_len + 1);
	if (s_customers[a_customer].formatted_digest == NULL)
	{
		F_ERROR(("realloc() failed!\n"));
	}
	else
	{
		// success! copy the new bytes
		strcpy(s_customers[a_customer].formatted_digest + digest_len, a_buffer);

		// end the digest with NULL char
		s_customers[a_customer].formatted_digest[digest_len + buffer_len] = '\0';
		
		result = true;
	}
		
    return result;
}
static void
s_FreeItems(int a_index)
{
    int i;

    for (i = 0; i < s_feeds[a_index].itemcount; i++)
    {
        xmlFree(s_feeds[a_index].items[i].url);
        xmlFree(s_feeds[a_index].items[i].pubdate);
        xmlFree(s_feeds[a_index].items[i].img_url);
    }
}

static int
s_CompareFeedDate(const void* p1, const void* p2)
{
	struct feed* feedptr1 = (struct feed*)p1;
	struct feed* feedptr2 = (struct feed*)p2;

	return DATE_CompareTime(feedptr2->pubdate, feedptr1->pubdate);
}

static int
s_CompareItemDate(const void* p1, const void* p2)
{
	struct item* itemptr1 = (struct item*)p1;
	struct item* itemptr2 = (struct item*)p2;

	return DATE_CompareTime(itemptr2->pubdate, itemptr1->pubdate);
}

static void
s_Normalize(char* a_text)
{
    char*  src = NULL;
    char*  lt = NULL;
    char*  gt = NULL;

    F_PRINT(("\nlen and source before:|%d|%s|\n", strlen(a_text), a_text));

    // normalize: if "&lt", "&gt", "\t", "\n" used replace them with "<", ">", " ", " " respectively
    while (src = strstr(a_text, "&lt"))
    {
        *src = '<';
    }

    while (src = strstr(a_text, "&gt"))
    {
        *(src + 2) = '>';
    }

    while (src = strstr(a_text, "\t"))
    {
        *src = ' ';
    }

    while (src = strstr(a_text, "\n"))
    {
        *src = ' ';
    }

    // look for "<" and ">"
    src = a_text;
    while (lt = strstr(src, "<"))
    {
        if (gt = strstr(lt, ">"))
        {
            // set the chars between '<' and '>' blank and trust s_TrimBlanks()!
            memset(lt, ' ', gt - lt + 1);

            // update
            src = gt + 1;
        }
        else
        {
            // idiots don't put a closing ">"!
            memset(lt, ' ', strlen(lt));
            break;
        }
    }

    F_PRINT(("\nlen and source after:|%d|%s|\n", strlen(a_text), a_text));
}

static void
s_TrimBlanks(char* a_text)
{
    char*  src;
    int    i;
    int    j;

    F_PRINT(("\nlen and source before:|%d|%s|\n", strlen(a_text), a_text));

    // ignore all leading blank chars if any found
    i = 0;
    src = a_text;
    while (*(src + i) == ' ' && *src != '\0')
    {
        i++;
    }

    if (i > 0)
    {
        memmove(src, src + i, strlen(src + i) + 1);
    }

    // delete all the blank substrings longer than 1 char
    for (src = a_text; *src != '\0'; src += i + j)
    {
        i = 0;
        while (*(src + i) != ' ' && *(src + i) != '\0')
        {
            i++;
        }

        j = 0;
        while (*(src + i + j) == ' ' && *(src + i + j) != '\0')
        {
            j++;
        }

        // one or no blank char is ok: do nothing!
        if (j == 0 || j == 1)
        {
            continue;
		}
		
        // multiple blank chars found: reduce them to one blank char!
        memmove(src + i, src + i + j - 1, strlen(src + i + j - 1) + 1);
    }

    // ignore all the trailing blank chars if any found
    src = a_text + strlen(a_text) - 1;
    if (*src == ' ')
    {
        *src = '\0';
    }

    F_PRINT(("\nlen and source after:|%d|%s|\n", strlen(a_text), a_text));
}

static void
s_Clean(char* a_text)
{
    s_Normalize(a_text);
    s_TrimBlanks(a_text);
}

static bool
s_ConvertPubdatetoLocalTime(char* pubdate, char* time_zone, char buffer_localtime[])
{
    if(DATE_ConvertLocalTime(pubdate) && DATE_ExtractLocalTime(time_zone, buffer_localtime, BUFFER_LOCAL_TIME_LENGTH))
    {
		return true;
    }
    else
    {
        F_ERROR(("failed to convert to local time!\n"));
		return false;
    }
}

static bool
s_KeywordSearch(int a_feed, int a_item, int a_customer, char* a_keyword)
{	
	// check the title or title+desc for the keyword?
	if (
		(s_customers[a_customer].digest_opt == digest_opt_title && strstr(s_feeds[a_feed].items[a_item].title, a_keyword) != NULL)
        ||
		(s_customers[a_customer].digest_opt == digest_opt_title_desc && (strstr(s_feeds[a_feed].items[a_item].title, a_keyword) != NULL ||
																       strstr(s_feeds[a_feed].items[a_item].description, a_keyword) != NULL))
		)
	{
		return true;
	} 

	return false;
}

static void
s_AddItems(int a_customer, int a_keyword, bool a_has_keyword)
{
    const char* keyword = s_customers[a_customer].keywords[a_keyword];
    customer_t* customer = &(s_customers[a_customer]);
	int item_count = 1; // NULL terminated!
    int feed;
    int item;

	// check within all the downloaded feeds
	for (feed = 0; feed < s_feed_count; feed++)
	{
		for (item = 0; item < s_feeds[feed].itemcount; item++)
		{
            bool result = false;

			// check the title or title+desc for the keyword?
			if (customer->digest_opt == digest_opt_title)
            {
                result = ((strstr(s_feeds[feed].items[item].title, keyword) != NULL) == a_has_keyword);
            }
            else if (customer->digest_opt == digest_opt_title_desc)
            {
                if (a_has_keyword)
                {
                    result = ((strstr(s_feeds[feed].items[item].title, keyword) != NULL)
                              ||
					          (strstr(s_feeds[feed].items[item].description, keyword) != NULL));
                }
                else
                {
                    result = ((strstr(s_feeds[feed].items[item].title, keyword) == NULL)
                              &&
					          (strstr(s_feeds[feed].items[item].description, keyword) == NULL));
                }
            }

            if (result)
			{
				// increment the item count
				item_count++;

				// try to allocate memory for the items
				customer->raw_digest[a_keyword] = (item_t**) realloc(customer->raw_digest[a_keyword], item_count * sizeof(item_t*));
				if (customer->raw_digest[a_keyword] == NULL)
				{
					F_ERROR(("malloc() failed!\n"));

                    // keep whatever found up to this point
					return;
				}

				customer->raw_digest[a_keyword][item_count - 2] = &(s_feeds[feed].items[item]);
				customer->raw_digest[a_keyword][item_count - 1] = NULL; // terminate with NULL
			} 
		} // for item
	} // for feed
}

static void
s_SimpleKeyword(int a_customer, int a_keyword)
{
    bool has_keyword = true;

    // yes
    switch (s_customers[a_customer].keywords[a_keyword][0])
    {
        case '-':
            has_keyword = false;
            s_customers[a_customer].keywords[a_keyword]++; // ignore the sign prefix
            break;

        case '+':
            s_customers[a_customer].keywords[a_keyword]++; // ignore the sign prefix
        default:
            has_keyword = true;
            break;
    }

    s_AddItems(a_customer, a_keyword, has_keyword);
}

static void
s_ComplexKeyword(int a_customer, int a_keyword)
{
	complex_keyword_t*  exp_keyword = NULL;
	char* temp;
    int  item_count = 1; // NULL terminated
	int  keywordCount = 1; // NULL terminated
    int  feed;
    int  item;
	int  count;

	// get the count of subkeywords
	temp = s_customers[a_customer].keywords[a_keyword];
	while (temp = strstr(temp, " "))
	{
		temp++;
		keywordCount++;
	}

    F_PRINT(("keyword count:|%d|\n", keywordCount));
                
	// try to allocate memory for all the subkeywords
	exp_keyword = (complex_keyword_t*) malloc(sizeof(complex_keyword_t) * (keywordCount));
	if (exp_keyword == NULL)
	{
		F_ERROR(("malloc() failed!\n"));
		goto exit;
	}

	// reset
	memset(exp_keyword, 0, sizeof(char*) * (keywordCount));
				
	// extract keywords and signs from expression
	exp_keyword[0].sign = s_customers[a_customer].keywords[a_keyword][0];
	exp_keyword[0].key = strtok(s_customers[a_customer].keywords[a_keyword], " ");
	memmove(exp_keyword[0].key, exp_keyword[0].key + 1, strlen(exp_keyword[0].key)); 

	count = 1;

	while ((exp_keyword[count].key = strtok(NULL, " ")) != NULL)
	{
		exp_keyword[count].sign = exp_keyword[count].key[0];
		memmove(exp_keyword[count].key, exp_keyword[count].key + 1, strlen(exp_keyword[count].key));
		count++;
	}

	// check within all the downloaded feeds
	for (feed = 0; feed < s_feed_count; feed++)
	{
		for (item = 0; item < s_feeds[feed].itemcount; item++)
		{
            bool switch_to_next_item = false;

			for (count = 0; count < keywordCount; count++)
			{
				if (exp_keyword[count].sign == '-' && s_KeywordSearch(feed, item, a_customer, exp_keyword[count].key) == true)
				{
                    switch_to_next_item = true;
                    break;
				}
                else if (exp_keyword[count].sign == '+' && s_KeywordSearch(feed, item, a_customer, exp_keyword[count].key) == false)
				{
                    switch_to_next_item = true;
                    break;
				}
			}

            if (switch_to_next_item)
            {
                continue;
            }

			// increment the item count
			item_count++;

			// try to allocate memory for the items
			s_customers[a_customer].raw_digest[a_keyword] = (item_t**) realloc(s_customers[a_customer].raw_digest[a_keyword], item_count * sizeof(item_t*));
			if (s_customers[a_customer].raw_digest[a_keyword] == NULL)
			{
				F_ERROR(("malloc() failed!\n"));

                // keep whatever found up to this point
				return;
			}

			s_customers[a_customer].raw_digest[a_keyword][item_count - 2] = &(s_feeds[feed].items[item]);
			s_customers[a_customer].raw_digest[a_keyword][item_count - 1] = NULL; // terminate with NULL
		} // for item
	}

exit:

    return;
}
